﻿using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;

namespace Tool.PETimer 
{
    //不能用 Unity单线程
    public class AsyncTimer : PETimer 
    {
        private bool setHandle;
        private readonly ConcurrentDictionary<int, AsyncTask> taskDic = new ConcurrentDictionary<int, AsyncTask>();
        private const string tidLock = "AsyncTimer_tidLock";
        
        private ConcurrentQueue<AsyncTaskPack> packQue;


        public AsyncTimer(bool setHandle) 
        {
            this.setHandle = setHandle;
            if(setHandle) 
            {
                packQue = new ConcurrentQueue<AsyncTaskPack>();
            }
        }

        public override int AddTask(uint delay, Action<int> taskCB, Action<int> cancelCB, int count = 1) 
        {
            int tid = GenerateTid();
            AsyncTask task = new AsyncTask(tid, delay, count, taskCB, cancelCB);
            RunTaskInPool(task);
            if(taskDic.TryAdd(tid, task)) 
                return tid;
            else 
            {
                WarnFunc?.Invoke($"key:{tid} already exist.");
                return -1;
            }
        }
        
        public override bool DeleteTask(int tid) {
            if(taskDic.TryRemove(tid, out AsyncTask task)) 
            {
                LogFunc?.Invoke($"Remvoe tid:{task.tid} task in taskDic Succ.");
                task.cts.Cancel();

                if(setHandle && task.cancelCB != null) 
                    packQue.Enqueue(new AsyncTaskPack(task.tid, task.cancelCB));
                else 
                    task.cancelCB?.Invoke(task.tid);
                return true;
            }
            else 
            {
                ErrorFunc?.Invoke($"Remove tid:{task.tid} task in taskDic failed.");
                return false;
            }
        }
        
        public override void Reset() 
        {
            if(packQue != null && !packQue.IsEmpty) 
                WarnFunc?.Invoke("Call Queue is not Empty.");
            taskDic.Clear();
            tid = 0;
        }

        void RunTaskInPool(AsyncTask task) 
        {
            Task.Run(async () => 
            {
                if(task.count > 0) 
                {
                    //限次数循环任务
                    do 
                    {
                        --task.count;
                        ++task.loopIndex;
                        int delay = (int)(task.delay + task.fixDelta);
                        if(delay > 0)
                            await Task.Delay(delay, task.ct);
                        TimeSpan ts = DateTime.UtcNow - task.startTime;
                        task.fixDelta = (int)(task.delay * task.loopIndex - ts.TotalMilliseconds);
                        CallBackTaskCB(task);
                        if (task.count == 0)
                            FinishTask(task.tid);
                    } while(task.count > 0);
                }
                else {
                    //永久循环任务
                    while(true) 
                    {
                        ++task.loopIndex;
                        int delay = (int)(task.delay + task.fixDelta);
                        if(delay > 0) 
                            await Task.Delay(delay, task.ct);
                        TimeSpan ts = DateTime.UtcNow - task.startTime;
                        task.fixDelta = (int)(task.delay * task.loopIndex - ts.TotalMilliseconds);
                        CallBackTaskCB(task);
                    }
                }
            });
        }
        
        void CallBackTaskCB(AsyncTask task) 
        {
            if(setHandle) 
                packQue.Enqueue(new AsyncTaskPack(task.tid, task.taskCB));
            else 
                task.taskCB.Invoke(task.tid);
        }

        void FinishTask(int tid)
        {
            if (taskDic.TryRemove(tid, out AsyncTask _))
                LogFunc?.Invoke($"Task tid:{tid} run to completion.");
            else
                ErrorFunc?.Invoke($"Remove tid:{tid} task in taskDic failed.");
        }

        public void HandleTask() 
        {
            while(packQue != null && packQue.Count > 0) 
            {
                if(packQue.TryDequeue(out AsyncTaskPack pack)) 
                    pack.cb?.Invoke(pack.tid);
                else
                    WarnFunc?.Invoke($"packQue dequeue data failed.");
            }
        }
        
        protected override int GenerateTid() 
        {
            lock(tidLock) 
            {
                while(true)
                {
                    ++tid;
                    if(tid == int.MaxValue) 
                        tid = 0;
                    if(!taskDic.ContainsKey(tid)) 
                        return tid;
                }
            }
        }

        class AsyncTask 
        {
            public int tid;
            public uint delay;
            public int count;

            public Action<int> taskCB;
            public Action<int> cancelCB;

            public DateTime startTime;
            public ulong loopIndex;
            public int fixDelta;

            public CancellationTokenSource cts;
            public CancellationToken ct;

            public AsyncTask(int tid, uint delay, int count, Action<int> taskCB, Action<int> cancelCB) 
            {
                this.tid = tid;
                this.delay = delay;
                this.count = count;
                this.taskCB = taskCB;
                this.cancelCB = cancelCB;

                startTime = DateTime.UtcNow;

                loopIndex = 0;
                fixDelta = 0;
                
                cts = new CancellationTokenSource();
                ct = cts.Token;
            }
        }
        
        class AsyncTaskPack 
        {
            public int tid;
            public Action<int> cb;
            public AsyncTaskPack(int tid, Action<int> cb) {
                this.tid = tid;
                this.cb = cb;
            }
        }
    }
}
